from __future__ import division
import math
import time
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from geolib.geom2d import Bounds
from .mapgraphicsitems import GraphicsLineStringItem
import geolib.geomeasure as geomeasure
import geolib.units as units

def packPointList(pointsIterable):
    out = []
    for point in pointsIterable:
        out.append(point[0])
        out.append(point[1])
    return out

currentTimeMs = lambda: int(round(time.time() * 1000))

class GraphicsMapFrame(QGraphicsView):
    """Map frame based on QGraphicsView.
        
    Signals:
    projectionChanged()
    boundsChanged()
    newCursorPosition(double, double) (x..lon, y...lat)
    contextMenuRequested(QPoint, double, double) QPoint=global position to pass to QMenu.exec_() (x..lon, y...lat)
    
    measuredDistanceChanged(double, double, double) distance, perimeter, area.
    measureComplete(double, double, double) distance, perimeter, area, on completion of measuring.
    newPoint(double, double) - (lon, lat) of new point
    newLineString(*double) - as many double pairs as it takes (lon, lat) to describe the new line string
    newPolygon(*double) - as many double pairs as it takes (lon, lat) to describe the new polygon
    mapClicked(double, double) - when a user clicks on the map if the tool is set to CUSTOM_CLICK
    toolChanged(int) - notifies when the current map tool has been changed
    """
    
    MEASURE_Z_INDEX = 1001
    
    MIN_REFRESH_PERIOD = 200 #ms
    
    TOOL_NONE = 0
    TOOL_PAN = 1
    TOOL_SELECT = 2
    TOOL_ZOOM_IN = 3
    TOOL_ZOOM_OUT = 4
    TOOL_ZOOM_RECT = 5
    TOOL_MEASURE = 6
    TOOL_NEW_POINT = 7
    TOOL_NEW_LINE = 8
    TOOL_NEW_POLYGON = 9
    TOOL_CUSTOM_CLICK = 10
    
    
    def __init__(self, projection, parent=None):
        super(GraphicsMapFrame, self).__init__(parent)
        self.setRenderHint(QPainter.Antialiasing)
        self.setRenderHint(QPainter.TextAntialiasing)
        
        self.__lastRefresh = 0
        self.__oldBounds = Bounds(0,0,0,0)
        
        self.__maxZoom = 21.0
        self.__wheelZoom = False
        self.setScene(QGraphicsScene(self))
        self.setProjection(projection)
        
        
        
        self.__createContextMenu = True
        self.__disableContextMenu = False
        
        self.__measuring = False
        self.__measureTracking = False
        self.__measureArea = False
        self.__measureGraphicsItem = GraphicsLineStringItem()
        self.__measureGraphicsItem.setVisible(False)
        self.__measureGraphicsItem.setZValue(self.MEASURE_Z_INDEX)        
        self.scene().addItem(self.__measureGraphicsItem)
        self.__measurePreviousMouseTrackingState = self.hasMouseTracking()
        
        self.__zoomPreviousBounds = None
        self.__zoomFirstScenePoint = None
        self.__zoomTracking = False
        self.__zoomGraphicsItem = QGraphicsRectItem()
        self.__zoomGraphicsItem.setZValue(self.MEASURE_Z_INDEX)
        self.__zoomGraphicsItem.setVisible(False)
        self.scene().addItem(self.__zoomGraphicsItem)
        self.__zoomToolDelta = 1.0
        
        self.__newPointStringItem = GraphicsLineStringItem()
        self.__newPointStringItem.setVisible(False)
        self.__newPointStringItem.setZValue(self.MEASURE_Z_INDEX)
        self.scene().addItem(self.__newPointStringItem)
        self.__newPointStringTracking = False
        
        self.__tool = self.TOOL_NONE
        
        self.__refreshScheduled = False
    
    def projection(self):
        '''Returns the projection that converts between scene coordinates and lat/lon coordinates'''
        return self.__proj
    
    def setProjection(self, projection, keepViewableArea=False):
        '''Set the projection that converts between scene coordinates and lat/lon coordinates'''
        if keepViewableArea:
            currentBounds = self.bounds() ;
        self.__proj = projection
        self.setSceneRect(self.projection().maxSceneRect())
        self.emit(SIGNAL("projectionChanged()"))
        if keepViewableArea:
            self.zoomToLatLonRect(currentBounds)
        else:
            self.__emitNewBounds()
    
    def sceneBounds(self):
        '''Returns a QRectF representing the currently viewable area'''
        upperLeft = self.mapToScene(QPoint(0,0))
        lowerRight = self.mapToScene(QPoint(self.width(), self.height()))
        return QRectF(upperLeft, lowerRight)

    def bounds(self):
        '''Returns a Bounds object representing the currently viewable area in lat/lon coordinates'''
        return self.projection().latLonBounds(self.sceneBounds())

    def zoomLevel(self):
        '''Returns a float representing the current zoom level'''
        latlonbounds = self.projection().latLonBounds(self.sceneBounds(), False)
        pxwidth = self.width()
        angleWidth = latlonbounds.width()
        if angleWidth < 0:
            angleWidth += 360
        
        zoom = math.log((360.0 / angleWidth) * (pxwidth / 256.0)) / math.log(2.0)
        return zoom
    
    def centre(self):
        pxX = int(self.width() / 2)
        pxY = int(self.height() / 2)
        scenePoint = self.mapToScene(pxX, pxY)
        return self.projection().latLonPoint(scenePoint)
    
    def metresPerPixel(self, bounds=None):
        """Returns a tuple (metres per X pixel, metres per Y pixel). Based on  the average distance between
        the corner pixels on each edge. This method may be inaccurate at large scales due to the wrap-around
        effect."""
        if bounds is None:
            bounds = self.bounds()
        horizontalDistance = (geomeasure.distance((bounds.topLeft(), bounds.topRight()), wraplat=False) + 
                              geomeasure.distance((bounds.bottomLeft(), bounds.bottomRight()), wraplat=False)) / 2.0
        verticalDistance = (geomeasure.distance((bounds.bottomLeft(), bounds.topLeft())) + 
                              geomeasure.distance((bounds.bottomRight(), bounds.topRight()))) / 2.0
        return (horizontalDistance / self.width(), verticalDistance / self.height())

    def physicalScale(self, scaleTup=None):
        """Estimates the reciprocal physical scale (X, Y) based on Qt-reported dpi, which
        documentation seems to suggest will be fairly inaccurate. A brief test revealed actual
        1:50000 scale on screen but this method returned 1:36000."""
        if scaleTup is None:
            scaleTup = self.metresPerPixel()
        dpiX = self.physicalDpiX()
        dpiY = self.physicalDpiY()
        return (scaleTup[0] * dpiX * 39.3701, scaleTup[1] * dpiY * 39.3701)
    
    def setZoomLevel(self, newLevel):
        '''Set the zoom level to a float (usually between 0 and 20, may differ for other projections)'''
        oldLevel = self.zoomLevel()
        if newLevel != oldLevel and newLevel <= self.maxZoom():
            delta = newLevel - oldLevel
            scale = 2.0 ** delta
            self.scale(scale, scale)
            self.__emitNewBounds()
        
    def maxZoom(self):
        '''Returns the maximum zoom level'''
        return self.__maxZoom
    
    def setMaxZoom(self, maxZoom):
        '''Set the maximum zoom level'''
        self.__maxZoom = maxZoom
    
    def setWheelZoom(self, state):
        '''Set if a wheel event zooms (default: wheel event scrolls)'''
        self.__wheelZoom = state
        
    def setCreateContextMenu(self, state):
        '''Set if context menu should be created on right click (default: false)'''
        self.__createContextMenu = state
    
    def _disableContextMenu(self):
        '''Used by other objects to supress this widget's context menu, for example, to implement
        its own right click functionality'''
        self.__disableContextMenu = True
    
    def zoomToLatLonRect(self, latLonRect):
        '''Zoom to Bounds object in lat/lon coordinates'''
        sceneRect = self.projection().sceneRect(latLonRect)
        self.zoomToSceneRect(sceneRect)
        
    def zoomToSceneRect(self, sceneRect):
        '''Zoom to QRectF object in scene coordinates'''
        minx = min(sceneRect.left(), sceneRect.right())
        maxx = max(sceneRect.left(), sceneRect.right())
        miny = min(sceneRect.top(), sceneRect.bottom())
        maxy = max(sceneRect.top(), sceneRect.bottom())
        self.fitInView(QRectF(QPointF(minx, miny), QPointF(maxx, maxy)), Qt.KeepAspectRatio)
        self.__emitNewBounds()
    
    def zoomIn(self):
        oldZoom = self.zoomLevel()
        if oldZoom <= self.maxZoom():
            newZoom = self.zoomLevel() + self.zoomToolDelta()
            self.setZoomLevel(min(newZoom, self.maxZoom()))
    
    def zoomOut(self):
        oldZoom = self.zoomLevel()
        if oldZoom >= 0:
            newZoom = self.zoomLevel() - self.zoomToolDelta()
            self.setZoomLevel(max(newZoom, 0))
    
    def nudge(self, dx, dy):
        self.scrollContentsBy(dx, dy)
        self.invalidateScene()
        self.__emitNewBounds()
    
    def centreOnLatLon(self, latLonPoint):
        self.centerOn(self.projection().scenePoint(latLonPoint))
        self.__emitNewBounds()
    
    def keyPressEvent (self, event):
        QGraphicsView.keyPressEvent(self, event)
        key = event.key()
        if key == Qt.Key_Right or key == Qt.Key_Left or key == Qt.Key_Down or key == Qt.Key_Up:
            self.__emitNewBounds()
        if key == Qt.Key_Escape and self.__measureTracking:
            self.stopCurrentMeasure()
            event.accept()
        elif key == Qt.Key_Escape and self.__newPointStringTracking:
            if self.currentTool() == self.TOOL_NEW_POLYGON:
                self.emit(SIGNAL("newPolygon"))
            else:
                self.emit(SIGNAL("newLineString"))
            self.__newPointStringItem.setVisible(False)
            self.__newPointStringTracking = False
            event.accept()
        
    def contextMenuEvent(self, event):
        super(GraphicsMapFrame, self).contextMenuEvent(event)
        if self.__createContextMenu and not self.__disableContextMenu:
            scenePt = self.mapToScene(event.pos())
            latLonPoint = self.projection().latLonPoint(scenePt)
            self.emit(SIGNAL("contextMenuRequested(QPoint, double, double)"), event.globalPos(), latLonPoint[0], latLonPoint[1])
        self.__disableContextMenu = False
    
    def resizeEvent(self, event):
        self.emit(SIGNAL("boundsChanged()"))
    
    def setTool(self, toolInt):
        if self.__tool != toolInt:
            self.__tool = toolInt
            if toolInt != self.TOOL_MEASURE and self.measuring():
                self.setMeasure(False)
                self.measureHideObject()
            elif toolInt == self.TOOL_MEASURE:
                self.setMeasure(True)
            
            if toolInt != self.TOOL_PAN and self.dragMode() == self.ScrollHandDrag:
                self.setDragMode(self.NoDrag)
            elif toolInt == self.TOOL_PAN:
                self.setDragMode(self.ScrollHandDrag)
            
            if toolInt != self.TOOL_SELECT and self.dragMode() == self.RubberBandDrag:
                self.setDragMode(self.NoDrag)
            elif toolInt == self.TOOL_SELECT:
                self.setDragMode(self.RubberBandDrag)
        
            if toolInt != self.TOOL_ZOOM_RECT:
                self.__zoomGraphicsItem.setVisible(False)
                self.__zoomTracking = False
                self.__zoomFirstScenePoint = None
                
            if toolInt != self.TOOL_NEW_LINE or toolInt != self.TOOL_NEW_POLYGON:
                self.__newPointStringItem.setVisible(False)
                self.__newPointStringTracking = False
            
            self.emit(SIGNAL("toolChanged(int)"), toolInt)
            
    
    def currentTool(self):
        return self.__tool
    
    def setZoomInDelta(self, delta):
        self.__zoomToolDelta = delta
    
    def zoomToolDelta(self):
        return self.__zoomToolDelta
    
    def setMeasure(self, measuring, latLonPoint=None):
        
        if measuring != self.measuring():
            self.__measuring = measuring
            if measuring:
                self.__measurePreviousMouseTrackingState = self.hasMouseTracking()
                self.setMouseTracking(True)
                if self.__measureTracking:
                    self.__measureGraphicsItem.clear()
                self.__measureTracking = False
                self.__measureGraphicsItem.setVisible(True)
                if latLonPoint is not None:
                    self.__measureGraphicsItem.commitLatLon(latLonPoint, self.projection())
                    self.__measureTracking = True
            else:
                self.stopCurrentMeasure()
                self.setMouseTracking(self.__measurePreviousMouseTrackingState)
    
    def measureHideObject(self):
        self.__measureGraphicsItem.setVisible(False)
    
    def setMeasureArea(self, flag):
        self.__measureArea = flag
        self.__measureGraphicsItem.setIsPolygon(flag)
    
    def measureArea(self):
        return self.__measureArea
    
    def measuring(self):
        return self.__measuring
    
    def mouseMoveEvent(self, event):
        scenePoint = self.mapToScene(event.pos())
        pointLatLon = self.projection().latLonPoint(scenePoint)
        self.emit(SIGNAL("newCursorPosition(double, double)"), pointLatLon[0], pointLatLon[1])
        if self.__measureTracking:
            self.__measureGraphicsItem.suggestScenePoint(scenePoint)
            self.__emitMeasureSignal(False, pointLatLon)
        elif self.__zoomTracking:
            self.__zoomGraphicsItem.setRect(QRectF(self.__zoomFirstScenePoint, scenePoint))
        elif self.__newPointStringTracking:
            self.__newPointStringItem.suggestScenePoint(scenePoint)
        super(GraphicsMapFrame, self).mouseMoveEvent(event)
    
    def mousePressEvent(self, event):
        scenePoint = self.mapToScene(event.pos())
        if event.button() == Qt.LeftButton and self.measuring():
            if self.__measureGraphicsItem.count() > 0 and not self.__measureTracking:
                self.__measureGraphicsItem.clear()
            else:
                self.__measureGraphicsItem.commitScenePoint(scenePoint, self.projection())
                self.__measureTracking = True
            self.__emitMeasureSignal(False)
            event.accept()
        elif event.button() == Qt.RightButton and self.measuring() and self.__measureTracking:
            self.__measureGraphicsItem.commitScenePoint(scenePoint, self.projection())
            self.__measureTracking = False
            self.__emitMeasureSignal(True)
            self._disableContextMenu()
            event.accept()
        elif event.button() == Qt.LeftButton and self.currentTool() == self.TOOL_ZOOM_RECT:
            self.__zoomTracking = True
            self.__zoomFirstScenePoint = scenePoint
            self.__zoomGraphicsItem.setRect(QRectF(self.__zoomFirstScenePoint, self.__zoomFirstScenePoint))
            self.__zoomGraphicsItem.setVisible(True)
            event.accept()
        elif event.button() == Qt.LeftButton and self.currentTool() == self.TOOL_ZOOM_IN:
            self.zoomIn()
            event.accept()
        elif event.button() == Qt.LeftButton and self.currentTool() == self.TOOL_ZOOM_OUT:
            self.zoomOut()
            event.accept()
        elif event.button() == Qt.LeftButton and self.currentTool() == self.TOOL_NEW_POINT:
            latLon = self.projection().latLonPoint(scenePoint)
            self.emit(SIGNAL("newPoint(double, double)"), latLon[0], latLon[1])
            event.accept()
        elif event.button() == Qt.LeftButton and self.currentTool() == self.TOOL_NEW_LINE:
            if not self.__newPointStringTracking:
                self.__newPointStringItem.clear()
                self.__newPointStringItem.setIsPolygon(False)
                self.__newPointStringItem.setVisible(True)
                self.__newPointStringTracking = True
            self.__newPointStringItem.commitScenePoint(scenePoint, self.projection())
            event.accept()
        elif event.button() == Qt.LeftButton and self.currentTool() == self.TOOL_NEW_POLYGON:
            if not self.__newPointStringTracking:
                self.__newPointStringItem.clear()
                self.__newPointStringItem.setIsPolygon(True)
                self.__newPointStringItem.setVisible(True)
                self.__newPointStringTracking = True
            self.__newPointStringItem.commitScenePoint(scenePoint, self.projection())
            event.accept()
        elif event.button() == Qt.RightButton and self.__newPointStringTracking:
            self.__newPointStringItem.commitScenePoint(scenePoint, self.projection())
            if self.currentTool() == self.TOOL_NEW_POLYGON:
                self.emit(SIGNAL("newPolygon"), *packPointList(self.__newPointStringItem.series()))
            else:
                self.emit(SIGNAL("newLineString"), *packPointList(self.__newPointStringItem.series()))
            self.__newPointStringItem.setVisible(False)
            self.__newPointStringTracking = False
            self._disableContextMenu()
            event.accept()
        elif event.button() == Qt.LeftButton and self.currentTool() == self.TOOL_CUSTOM_CLICK:
            lon, lat = self.projection().latLonPoint(scenePoint)
            self.emit(SIGNAL("mapClicked(double, double)"), lon, lat)
        else:
            super(GraphicsMapFrame, self).mousePressEvent(event)
    
    def stopCurrentMeasure(self):
        self.__measureGraphicsItem.redraw()
        self.__measureTracking = False
        self.__emitMeasureSignal(True)
        
        
    def __emitMeasureSignal(self, finish, suggestPoint=None):
        if finish:
            signal = "measureComplete(double, double, double)"
        else:
            signal = "measuredDistanceChanged(double, double, double)"
            
        distance = self.__measureGraphicsItem.geoDistance(suggestPoint)
        perimeter = self.__measureGraphicsItem.geoPerimeter(suggestPoint)
        area = self.__measureGraphicsItem.geoArea(suggestPoint)
        self.emit(SIGNAL(signal), distance, perimeter, area)
        
    def mouseReleaseEvent(self, event):
        if self.currentTool() == self.TOOL_PAN:
            self.__emitNewBounds()
        elif self.__zoomTracking:
            self.__zoomGraphicsItem.setVisible(False)
            self.__zoomTracking = False
            self.zoomToSceneRect(self.__zoomGraphicsItem.rect())
            self.__zoomFirstScenePoint = None
        super(GraphicsMapFrame, self).mouseReleaseEvent(event)
    
    def __emitNewBounds(self):
        now = currentTimeMs()
        if now - self.__lastRefresh > self.MIN_REFRESH_PERIOD:
            bounds = self.bounds()
            if bounds != self.__oldBounds:
                self.__oldBounds = bounds
                self.emit(SIGNAL("boundsChanged()"))
                self.__refreshScheduled = False
                self.__lastRefresh = now
        elif not self.__refreshScheduled:
            QTimer.singleShot(self.MIN_REFRESH_PERIOD, self.__emitNewBounds)
            self.__refreshScheduled = True
            
    def wheelEvent(self, event):
        if self.__wheelZoom :
            factor = 1.41 ** (-event.delta() / 240.0)
            level = self.zoomLevel()
            if level < self.maxZoom() or (level >= self.maxZoom() and factor < 1.0):
                self.scale(factor, factor)
        else:
            super(GraphicsMapFrame, self).wheelEvent(event)
        self.__emitNewBounds()
        

class LayerListWidgetItem(QListWidgetItem):
    
    def __init__(self, layer, parent=None):
        super(LayerListWidgetItem, self).__init__(layer.name(), parent)
        self.setData(LayerManagerListWidget.ROLE_ATTRIBUTE, QVariant(layer.layerId()))
        checkState = Qt.Unchecked
        if layer.visible():
            checkState = Qt.Checked
        self.setCheckState(checkState)
        if layer.icon() is not None:
            self.setIcon(layer.icon())
        self.setFlags(Qt.ItemIsDragEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable|Qt.ItemIsEnabled|Qt.ItemIsDropEnabled)


class LayerManagerListWidget(QListWidget):
    """Wraps a LayerManager object in a ListWidget to provide functionality.
    
    Signals:
    layerContextMenuRequested(QPoint, QString) (QPoint...where to place the menu, QString is layerId, empty string for no layer)
    layerDoubleClicked(QString) (QString is layerId)
    """
    
    ROLE_ATTRIBUTE = 1001
    
    def __init__(self, layerManager, parent=None):
        super(LayerManagerListWidget, self).__init__(parent)
        self.__manager = layerManager
        self.setDefaultDropAction(Qt.MoveAction)
        self.setDragDropMode(QAbstractItemView.InternalMove)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        
        self.syncFromManager()
        self.connect(self, SIGNAL("itemChanged(QListWidgetItem*)"), self.listItemChanged)
        self.connect(self, SIGNAL("itemDoubleClicked(QListWidgetItem*)"), self.listItemDoubleClicked)
    
    def manager(self):
        return self.__manager
    
    def layers(self):
        return self.manager().layers.values()
    
    def layerById(self, layerId):
        return self.manager().layerById(str(layerId))
    
    def layerByIndex(self, index):
        return self.manager().layerByIndex(index)
    
    def append(self, layer):
        self.manager().append(layer)
        self.syncFromManager()
    
    def insert(self, layer, index):
        self.manager().insert(layer, index)
        self.syncFromManager()
    
    def replace(self, layer, index):
        self.manager().replace(layer, index)
        self.syncFromManager()
    
    def removeByIndex(self, index):
        self.manager().removeByIndex(index)
        self.syncFromManager()
    
    def clearLayers(self):
        self.manager().clear()
        self.syncFromManager()
        
    def removeById(self, layerId):
        self.manager().removeById(str(layerId))
        self.syncFromManager()
    
    def syncFromManager(self):
        self.clear()
        for layerId in reversed(self.manager().layerIdList):
            layer = self.layerById(layerId)
            self.addItem(self.newListWidgetItem(layer))
    
    def syncToManager(self):
        newIndex = self.manager().startingIndex()
        for index in reversed(range(self.count())):
            item = self.item(index)
            layer = self.layerFromItem(item)
            oldIndex = layer.zIndex()
            if oldIndex != newIndex:
                layer.setZIndex(newIndex)
                layer.updateAttributes()
            newIndex += 1
        self.manager().sortByZIndicies()
        print ""
    
    def newListWidgetItem(self, layer):
        return LayerListWidgetItem(layer)
    
    def selectedLayerIds(self):
        out = []
        for item in reversed(self.selectedItems()):
            out.append(self.layerIdFromItem(item))
        return out
    
    def selectedLayers(self):
        out = []
        for item in reversed(self.selectedItems()):
            out.append(self.layerFromItem(item))
        return out
    
    def newLayerIndexBySelection(self):
        indexes = self.selectedIndexes()
        if len(indexes) == 1:
            return self.count() - indexes[0].row() - 1
        else:
            return None
    
    def nextLayerId(self):
        return self.manager().nextLayerId()
    
    def itemFromLayerId(self, layerId):
        for index in range(self.count()):
            item = self.item(index)
            if self.layerIdFromItem(item) == layerId:
                return item
            
    
    def layerIdFromItem(self, item):
        if item is not None:
            return str(item.data(self.ROLE_ATTRIBUTE).toString())
        else:
            return None
    
    def layerFromItem(self, item):
        layerId = self.layerIdFromItem(item)
        return self.layerById(layerId)
    
    def renameItem(self, layerId):
        item = self.itemFromLayerId(layerId)
        if item is not None:
            item.setFlags(item.flags()|Qt.ItemIsEditable)
            self.editItem(item)
        else:
            raise KeyError, "No layer with layerId %s was found" % layerId
    
    def resetEditable(self):
        for index in range(self.count()):
            item = self.item(index)
            newFlags = item.flags() & ~Qt.ItemIsEditable
            item.setFlags(newFlags)
    
    def listItemChanged(self, item):
        layer = self.layerFromItem(item)
        layer.setName(item.text())
        checked = item.checkState() == Qt.Checked
        if checked != layer.visible():
            layer.setVisible(checked)
            layer.updateAttributes()
    
    def focusInEvent(self, event):
        self.resetEditable()
        super(LayerManagerListWidget, self).focusInEvent(event)
    
    def focusOutEvent(self, event):
        self.resetEditable()
        super(LayerManagerListWidget, self).focusOutEvent(event)
    
    def dropEvent(self, event):
        super(LayerManagerListWidget, self).dropEvent(event)
        #TODO: rework this event handler so internal move is accepted before synced to manager
        #self.syncToManager()
    
    def contextMenuEvent(self, event):
        item = self.itemAt(event.pos())
        layerId = self.layerIdFromItem(item)
        if layerId is None:
            layerId = ""
        self.emit(SIGNAL("layerContextMenuRequested(QPoint, QString)"), event.globalPos(), layerId)
    
    def listItemDoubleClicked(self, item):
        layerId = self.layerIdFromItem(item)
        self.emit(SIGNAL("layerDoubleClicked(QString)"), layerId)
    

class ScaleBarWidget(QWidget):
    
    WIDTH_DEFAULT = 300
    
    STYLE_TICKS = 1
    STYLE_CHECKER = 2
        
    def __init__(self, parent=None):
        super(ScaleBarWidget, self).__init__(parent)
        
        palette = QPalette(self.palette())
        palette.setColor(palette.Background, Qt.transparent)
        self.setPalette(palette)
        self.setAttribute(Qt.WA_TransparentForMouseEvents)
        
        self.__widthHint = self.WIDTH_DEFAULT
        self.__unitCategory = units.CATEGORY_METRIC
        self.__currentMPerPixel = 1
        self.__padding = (5,5)
        self.__style = self.STYLE_CHECKER
        self.scaleParameters = (0,0,0,0,"m")
        self.path = QPainterPath()
        self.pathFilled = QPainterPath()
        self.setMinimumHeight(15)
        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
    
    def setScaleStyle(self, styleInt):
        self.__style = styleInt
    
    def setUnitCategory(self, category):
        self.__unitCategory = category
    
    def setPadding(self, px, py):
        self.__padding = (px, py)
    
    def widthHint(self):
        return self.__widthHint
    
    def setWidthHint(self, hint):
        self.__widthHint = hint
    
    def sizeHint(self):
        return QSize(self.scaleParameters[2] + (self.__padding[0]*2), 10 + (self.__padding[1]*2))

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        painter.setRenderHint(QPainter.Antialiasing)
        pen = QPen(QColor(0,0,0))
        pen.setCapStyle(Qt.RoundCap)
        pen.setJoinStyle(Qt.RoundJoin)
        pen.setWidth(1)
        painter.strokePath(self.path, pen)
        painter.setBrush(QColor(0,0,0))
        painter.drawPath(self.pathFilled)
        painter.end()
    
    def refreshScale(self, mPerPixelX=None, unitCategory=None):
        if unitCategory is not None:
            self.__unitCategory = unitCategory
        if mPerPixelX is not None:
            self.__currentMPerPixel = mPerPixelX
        self.recalculate()
        if self.__style == self.STYLE_CHECKER:  
            self.setPathsChecker(*self.scaleParameters)
        else:
            self.setPathsTick(*self.scaleParameters)
        self.updateGeometry()
        
    def recalculate(self):
        unitCategory = self.__unitCategory
        mPerPixelX = self.__currentMPerPixel
        
        availableWidth = 0.8 * self.widthHint()
        geoWidthM = mPerPixelX * availableWidth
        if geoWidthM < 1:
            unitC = ("cm", "in")
        elif geoWidthM < 1600:
            unitC = ("m", "ft")
        else:
            unitC = ("km", "mi")
        unit = unitC[unitCategory-1]
        widthHintU = units.convertTo(geoWidthM, unit)
        tenFactor = math.floor(math.log10(widthHintU))
        widthInTens = math.floor(widthHintU / (10**tenFactor))
        if widthInTens == 1:
            widthInTens = 10
            tenFactor -= 1
        elif widthInTens == 7:
            widthInTens = 6
        elif widthInTens == 9:
            widthInTens = 8
        
        if widthInTens < 6:
            majDivTens = 1
        else:
            majDivTens = 2
            
        widthU = widthInTens * 10**tenFactor
        majorDiv = majDivTens * 10**tenFactor
        majorDivs = round(widthU / majorDiv)
        widthPx = units.convertFrom(widthU, unit) / mPerPixelX
        majorDivPx = widthPx / majorDivs
        self.scaleParameters = (widthU, majorDiv, widthPx, majorDivPx, unit)
    
    def setPathsTick(self, widthU, majorDivU, widthPx, majorDivPx, unit):
        
        #set parameters, local coordinates
        leftScaleBar = self.__padding[0]
        topScaleBar = self.__padding[1]
        bottomScaleBar = self.height() - self.__padding[1]
        majorDivHeight = (bottomScaleBar - topScaleBar) * 0.5
        
        #draw outline
        path = QPainterPath(QPointF(leftScaleBar, topScaleBar)) #in local coordinates for this graphics item
        path.lineTo(QPointF(leftScaleBar, bottomScaleBar))
        path.lineTo(QPointF(leftScaleBar + widthPx, bottomScaleBar))
        path.lineTo(QPointF(leftScaleBar + widthPx, topScaleBar))
        
        #draw major divisions
        majDivCount = int(round(widthPx / majorDivPx))
        for index in range(1, majDivCount):
            pxX = index * majorDivPx + leftScaleBar
            path.moveTo(QPointF(pxX, bottomScaleBar))
            path.lineTo(QPointF(pxX, bottomScaleBar - majorDivHeight))
        
        self.path = path
        self.pathFilled = QPainterPath()
    
    def setPathsChecker(self, widthU, majorDivU, widthPx, majorDivPx, unit):
        leftScaleBar = self.__padding[0]
        topScaleBar = self.__padding[1]
        bottomScaleBar = self.height() - self.__padding[1]
        minorDivHeight = (bottomScaleBar - topScaleBar) / 2
        minorDivWidth = majorDivPx / 5
        
        
        path = QPainterPath()
        filledPath = QPainterPath()
        
        alt = False
        majStartIndex = 0
        
        if majorDivPx >= 60:
            for index in range(0, 5):
                pxX = index * minorDivWidth + leftScaleBar
                rect = QRectF(QPointF(pxX, topScaleBar), QPointF(pxX+minorDivWidth, topScaleBar+minorDivHeight))
                rect2 = QRectF(rect)
                rect2.translate(0, minorDivHeight)
                
                if alt:
                    path.addRect(rect)
                    filledPath.addRect(rect2)
                else:
                    path.addRect(rect2)
                    filledPath.addRect(rect)
                alt = not alt
            majStartIndex += 1
        
        majDivCount = int(round(widthPx / majorDivPx))
        for index in range(majStartIndex, majDivCount):
            pxX = index * majorDivPx + leftScaleBar
            pxXEnd = pxX + majorDivPx
            rect = QRectF(QPointF(pxX, topScaleBar), QPointF(pxXEnd, bottomScaleBar))
            if alt:
                path.addRect(rect)
            else:
                filledPath.addRect(rect)
            alt = not alt
        self.path = path
        self.pathFilled = filledPath

class ScaleWidget(QWidget):
    
    SCALE_STYLE_TICKS = ScaleBarWidget.STYLE_TICKS
    SCALE_STYLE_CHECKER = ScaleBarWidget.STYLE_CHECKER
    
    def __init__(self, parent=None):
        super(ScaleWidget, self).__init__(parent)
        layout = QHBoxLayout(self)
        self.scalebar = ScaleBarWidget(self)
        layout.addWidget(self.scalebar)
        self.label = QLabel(self)
        layout.addWidget(self.label)
        self.setMinimumHeight(self.scalebar.minimumHeight())
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        
        layout.setContentsMargins(2, 2, 2, 2)
        self.setLayout(layout)
    
    def setScaleBarPadding(self, paddingX, paddingY):
        self.scalebar.setPadding(paddingX, paddingY)
    
    def setScaleBarStyle(self, scaleBarStyleInt):
        self.scalebar.setScaleStyle(scaleBarStyleInt)
    
    def setUnitCategory(self, category):
        self.scalebar.setUnitCategory(category)
    
    def refreshScale(self, mPerPixel=None):
        self.scalebar.refreshScale(mPerPixel)
        maxWidth = str(int(self.scalebar.scaleParameters[0]))
        unit = self.scalebar.scaleParameters[4]
        self.label.setText(QString(maxWidth + " " + unit))
        
class ScaleOverlayMapFrame(QWidget):
    
    def __init__(self, projection, parent=None):
        super(ScaleOverlayMapFrame, self).__init__(parent)
        self.frame = GraphicsMapFrame(projection, self)
        self.scale = ScaleWidget(self)
        self.scale.raise_()
        
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        
        self.connect(self.frame, SIGNAL("boundsChanged()"), self.syncScale)
    
    def syncScale(self):
        scaletup = self.frame.metresPerPixel()
        self.scale.refreshScale(scaletup[0])
        scaleHint = self.scale.sizeHint()
        self.scale.setGeometry(20,20, scaleHint.width()+4, scaleHint.height())
    
    def resizeEvent(self, event):
        self.frame.move(0,0)
        self.frame.resize(event.size())
        
        
        
        
        